#pragma once
#include "logger.hpp"
#include <etcd/Client.hpp>
#include <etcd/KeepAlive.hpp>
#include <etcd/Response.hpp>
#include <etcd/Value.hpp>
#include <etcd/Watcher.hpp>
#include <functional>

namespace ken_im
{
// 服务注册客户端类
class Registry
{
  public:
    // 定义Registry类的智能指针类型
    using ptr = std::shared_ptr<Registry>;
    // 构造函数，传入host参数，创建etcd::Client对象，并创建etcd::KeepAlive对象和lease_id
    Registry( const std::string &host )
        : _client( std::make_shared<etcd::Client>( host ) ),
          _keep_alive( _client->leasekeepalive( 3 ).get() ),
          _lease_id( _keep_alive->Lease() )
    {
    }
    // 析构函数，取消etcd::KeepAlive对象
    ~Registry()
    {
        _keep_alive->Cancel();
    }
    // 注册数据，传入key和val参数，调用etcd::Client对象的put方法，传入key、val和lease_id，返回结果
    bool registry( const std::string &key, const std::string &val )
    {
        auto resp = _client->put( key, val, _lease_id ).get();
        // 如果返回结果不成功，则输出错误信息，并返回false
        if ( resp.is_ok() == false )
        {
            LOG_ERROR( "注册数据失败：{}", resp.error_message() );
            return false;
        }
        // 否则返回true
        return true;
    }

  private:
    // 定义etcd::Client对象的智能指针
    std::shared_ptr<etcd::Client> _client;
    // 定义etcd::KeepAlive对象的智能指针
    std::shared_ptr<etcd::KeepAlive> _keep_alive;
    // 定义lease_id
    uint64_t _lease_id;
};

// 服务发现客户端类
/**
 * @class Discovery
 * @brief Discovery 类用于在 etcd 中进行服务发现和监控。
 *
 * Discovery 类通过 etcd 客户端进行服务发现，并监控 etcd
 * 中指定目录下的数据变化。 它支持新增服务和下线服务的回调通知。
 *
 * 核心功能：
 * - 初始化时获取当前目录下的所有服务信息。
 * - 监控目录下的数据变化，并调用相应的回调函数处理新增和下线服务。
 *
 * 使用示例：
 * @code
 * auto put_cb = [](const std::string &key, const std::string &value) {
 *     std::cout << "新增服务: " << key << " - " << value << std::endl;
 * };
 * auto del_cb = [](const std::string &key, const std::string &value) {
 *     std::cout << "下线服务: " << key << " - " << value << std::endl;
 * };
 * Discovery::ptr discovery = std::make_shared<Discovery>(
 *     "localhost:2379", "/services", put_cb, del_cb
 * );
 * @endcode
 *
 * 构造函数参数：
 * - @param host etcd 服务器地址，例如 "localhost:2379"。
 * - @param basedir etcd 中用于服务发现的目录，例如 "/services"。
 * - @param put_cb 新增服务时的回调函数，参数为服务 key 和 value。
 * - @param del_cb 下线服务时的回调函数，参数为服务 key 和 value。
 *
 * 注意事项：
 * - 构造函数会立即获取当前目录下的所有服务信息，并调用 put_cb 回调函数。
 * - 构造函数会启动一个监控器，监控目录下的数据变化，并调用相应的回调函数。
 * - 析构函数会取消监控器，确保资源被正确释放。
 */
class Discovery
{
  public:
    using ptr = std::shared_ptr<Discovery>;
    using NotifyCallback = std::function<void( std::string, std::string )>;

    Discovery( const std::string &host, const std::string &basedir,
               const NotifyCallback &put_cb, const NotifyCallback &del_cb )
        : _client( std::make_shared<etcd::Client>( host ) ), _put_cb( put_cb ),
          _del_cb( del_cb )
    {
        auto resp = _client->ls( basedir ).get();
        if ( resp.is_ok() == false )
        {
            LOG_ERROR( "获取服务信息数据失败：{}", resp.error_message() );
        }
        int sz = resp.keys().size();
        for ( int i = 0; i < sz; ++i )
        {
            if ( _put_cb )
                _put_cb( resp.key( i ), resp.value( i ).as_string() );
        }
        _watcher = std::make_shared<etcd::Watcher>(
            *_client.get(), basedir,
            std::bind( &Discovery::callback, this, std::placeholders::_1 ),
            true );
    }

    ~Discovery()
    {
        _watcher->Cancel();
    }

  private:
    void callback( const etcd::Response &resp )
    {
        if ( resp.is_ok() == false )
        {
            LOG_ERROR( "收到一个错误的事件通知: {}", resp.error_message() );
            return;
        }
        for ( auto const &ev : resp.events() )
        {
            if ( ev.event_type() == etcd::Event::EventType::PUT )
            {
                if ( _put_cb )
                    _put_cb( ev.kv().key(), ev.kv().as_string() );
                LOG_DEBUG( "新增服务：{}-{}", ev.kv().key(),
                           ev.kv().as_string() );
            }
            else if ( ev.event_type() == etcd::Event::EventType::DELETE_ )
            {
                if ( _del_cb )
                    _del_cb( ev.prev_kv().key(), ev.prev_kv().as_string() );
                LOG_DEBUG( "下线服务：{}-{}", ev.prev_kv().key(),
                           ev.prev_kv().as_string() );
            }
        }
    }

  private:
    NotifyCallback _put_cb;
    NotifyCallback _del_cb;
    std::shared_ptr<etcd::Client> _client;
    std::shared_ptr<etcd::Watcher> _watcher;
};
} // namespace ken_im